﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Reflection;

namespace Antix.Data.Objects
{
    public static class TypeExtensions
    {
        #region Nullable

        /// <summary>
        /// Identifies a nullable type
        /// </summary>
        public const string NullableTypeName = "Nullable`1";

        /// <summary>
        /// Check a type for nullable
        /// </summary>
        public static bool IsNullable(this Type type)
        {
            return type.Name == NullableTypeName;
        }

        /// <summary>
        /// Gets the type even if a nullable type
        /// </summary>
        public static Type GetNotNullableType(this Type type)
        {
            if (IsNullable(type))
            {
                return type.GetGenericArguments()[0];
            }
            else { return type; }
        }

        #endregion

        #region FindAttribute

        /// <summary>
        /// <para>Find an attribute of a particular type associated with the type passed</para>
        /// <para>Handles IQueryable, generic parameters and checks interfaces too</para>
        /// </summary>
        /// <param name="type">Type</param>
        /// <param name="attType">Type of attribute to find</param>
        /// <returns>Attribute if found, null if not</returns>
        public static object FindAttribute(this Type type, Type attType)
        {
            if (typeof(IQueryable).IsAssignableFrom(type))
            {
                // get the element type
                type = type.GetGenericArguments()[0];
            }

            if (type.IsGenericParameter
                && type.GetGenericParameterConstraints().Length > 0)
            {
                // Get the generic constraint for this type
                type = type.GetGenericParameterConstraints()[type.GenericParameterPosition];
            }

            // find the table attribute
            object[] tableAtts = type.GetCustomAttributes(attType, true);
            if (tableAtts.Length > 0)
            {
                return tableAtts[0];
            }
            else
            {
                // Check interfaces for attribute
                foreach (Type typeInt in type.GetInterfaces())
                {
                    tableAtts = typeInt.GetCustomAttributes(attType, true);
                    if (tableAtts.Length > 0)
                    {
                        return tableAtts[0];
                    }
                }
            }

            return null;
        }

        #endregion

        #region GetTable..()

        /// <summary>
        /// <para>Get table name</para>
        /// </summary>
        /// <param name="type">Type</param>
        /// <returns>null if non found</returns>
        public static string GetDataTableName(this Type type)
        {
            DataTableAttribute att = GetDataTable(type);
            if (att != null) { return att.Name; }

            return null;
        }

        private static Dictionary<Type, DataTableAttribute> _DataTables;

        /// <summary>
        /// <para>Get the DataTableAttribute associated with the type</para>
        /// </summary>
        /// <param name="type">Type</param>
        /// <returns>DataTableAttribute if found</returns>
        public static DataTableAttribute GetDataTable(this Type type)
        {
            if (_DataTables == null) { _DataTables = new Dictionary<Type, DataTableAttribute>(); }
            if (!_DataTables.ContainsKey(type))
            {
                _DataTables[type] = (DataTableAttribute)type.FindAttribute(typeof(DataTableAttribute));
            }

            return _DataTables[type];
        }

        #endregion

        #region GetColumn..()

        /// <summary>
        /// <para>Get the column attribute on the field</para>
        /// </summary>
        public static DataColumnAttribute GetDataColumn(this FieldInfo fieldInfo)
        {
            object[] colAtts = fieldInfo.GetCustomAttributes(typeof(DataColumnAttribute), true);
            if (colAtts.Length > 0)
            {
                DataColumnAttribute column = (DataColumnAttribute)colAtts[0];
                column.FieldInfo = fieldInfo;
                return column;
            }
            else { return null; }
        }

        private static Dictionary<Type, DataColumnAttribute[]> _DataColumns;

        /// <summary>
        /// <para>Get all the columns for the type passed</para>
        /// <para>Cache retrieved columns</para>
        /// </summary>
        /// <param name="type">Type of object</param>
        /// <returns>Data Columns found</returns>
        public static DataColumnAttribute[] GetDataColumns(this Type type)
        {
            if (_DataColumns == null) { _DataColumns = new Dictionary<Type, DataColumnAttribute[]>(); }
            if (!_DataColumns.ContainsKey(type))
            {
                List<DataColumnAttribute> columns = new List<DataColumnAttribute>();
                foreach (FieldInfo fieldInfo in type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic))
                {
                    DataColumnAttribute column = fieldInfo.GetDataColumn();
                    if (column != null) { columns.Add(column); }
                }

                _DataColumns.Add(type, columns.ToArray());
            }

            return _DataColumns[type];
        }

        #endregion

        #region GetIndex..()

        private static Dictionary<Type, DataIndexAttribute[]> _DataIndexes;

        /// <summary>
        /// <para>Get all index attributes on the type passed</para>
        /// </summary>
        public static DataIndexAttribute[] GetDataIndex(this Type type)
        {
            if (_DataIndexes == null) { _DataIndexes = new Dictionary<Type, DataIndexAttribute[]>(); }
            if (!_DataIndexes.ContainsKey(type))
            {
                Dictionary<string, DataIndexAttribute> atts = new Dictionary<string, DataIndexAttribute>();
                while (type != typeof(System.Object))
                {
                    foreach (DataIndexAttribute att
                        in (DataIndexAttribute[])type.GetCustomAttributes(typeof(DataIndexAttribute), false))
                    {
                        // only add if one of the same name is not already found
                        if (!atts.ContainsKey(att.Name))
                        {
                            atts.Add(att.Name, att);
                        }
                    }
                    type = type.BaseType;
                }

                DataIndexAttribute[] attsArray
                    = (DataIndexAttribute[])Array.CreateInstance(typeof(DataIndexAttribute), atts.Count);
                atts.Values.CopyTo(attsArray, 0);

                _DataIndexes[type] = attsArray;
            }

            return _DataIndexes[type];
        }

        #endregion
    }
}
